/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.ranger.plugin.store;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ranger.plugin.model.RangerServiceResource;
import org.apache.ranger.plugin.model.RangerTag;
import org.apache.ranger.plugin.model.RangerTagDef;
import org.apache.ranger.plugin.model.RangerTagResourceMap;

import java.util.List;

public class TagValidator {
    private TagStore tagStore;

    public TagValidator() {}

    public void setTagStore(TagStore tagStore) {
        this.tagStore = tagStore;
    }

    public RangerTagDef preCreateTagDef(final RangerTagDef tagDef, boolean updateIfExists) throws Exception {
        String name = tagDef.getName();

        if (StringUtils.isBlank(name)) {
            throw new Exception("TagDef has no name");
        }

        return tagStore.getTagDefByName(name);
    }

    public RangerTag preCreateTag(final RangerTag tag) throws Exception {
        if (StringUtils.isBlank(tag.getType())) {
            throw new Exception("Tag has no type");
        }

        RangerTag ret = null;

        String guid = tag.getGuid();
        if (!StringUtils.isBlank(guid)) {
            ret = tagStore.getTagByGuid(guid);
        }

        return ret;
    }

    public void preUpdateTag(final Long id, final RangerTag tag) throws Exception {
        if (StringUtils.isBlank(tag.getType())) {
            throw new Exception("Tag has no type");
        }

        if (id == null) {
            throw new Exception("Invalid/null id");
        }

        RangerTag existing = tagStore.getTag(id);

        if (existing == null) {
            throw new Exception("Attempt to update nonexistant tag, id=" + id);
        }

        if (!StringUtils.equals(tag.getType(), existing.getType())) {
            throw new Exception("Attempt to change the tag-type");
        }

        tag.setId(existing.getId());
        tag.setGuid(existing.getGuid());
    }

    public void preUpdateTagByGuid(String guid, final RangerTag tag) throws Exception {
        if (StringUtils.isBlank(tag.getType())) {
            throw new Exception("Tag has no type");
        }

        RangerTag existing = tagStore.getTagByGuid(guid);
        if (existing == null) {
            throw new Exception("Attempt to update nonexistent tag, guid=" + guid);
        }

        if (!StringUtils.equals(tag.getType(), existing.getType())) {
            throw new Exception("Attempt to change the tag-type");
        }

        tag.setId(existing.getId());
        tag.setGuid(existing.getGuid());
    }

    public RangerTag preDeleteTag(Long id) throws Exception {
        if (id == null) {
            throw new Exception("Invalid/null id");
        }

        RangerTag existing = tagStore.getTag(id);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent tag, id=" + id);
        }

        List<RangerTagResourceMap> associations = tagStore.getTagResourceMapsForTagId(existing.getId());
        if (CollectionUtils.isNotEmpty(associations)) {
            throw new Exception("Attempt to delete tag which is associated with a service-resource, id=" + id);
        }
        return existing;
    }

    public RangerTag preDeleteTagByGuid(String guid) throws Exception {
        RangerTag exiting = tagStore.getTagByGuid(guid);

        if (exiting == null) {
            throw new Exception("Attempt to delete nonexistent tag, guid=" + guid);
        }

        List<RangerTagResourceMap> associations = tagStore.getTagResourceMapsForTagId(exiting.getId());
        if (CollectionUtils.isNotEmpty(associations)) {
            throw new Exception("Attempt to delete tag which is associated with a service-resource, guid=" + guid);
        }
        return exiting;
    }

    public RangerServiceResource preCreateServiceResource(RangerServiceResource resource) throws Exception {
        RangerServiceResource ret = null;

        if (StringUtils.isBlank(resource.getServiceName()) || MapUtils.isEmpty(resource.getResourceElements())) {
            throw new Exception("No serviceName or resource in RangerServiceResource");
        }

        String guid = resource.getGuid();
        if (!StringUtils.isBlank(guid)) {
            ret = tagStore.getServiceResourceByGuid(guid);
        }

        if (ret == null) {
            RangerServiceResourceSignature serializer = new RangerServiceResourceSignature(resource);
            resource.setResourceSignature(serializer.getSignature());
            ret = tagStore.getServiceResourceByServiceAndResourceSignature(resource.getServiceName(), resource.getResourceSignature());
        }

        return ret;
    }

    public void preUpdateServiceResource(Long id, RangerServiceResource resource) throws Exception {
        if (StringUtils.isBlank(resource.getServiceName()) || MapUtils.isEmpty(resource.getResourceElements())) {
            throw new Exception("No serviceName or resource in RangerServiceResource");
        }

        if (id == null) {
            throw new Exception("Invalid/null id");
        }

        RangerServiceResource existing = tagStore.getServiceResource(id);
        if (existing == null) {
            throw new Exception("Attempt to update nonexistent resource, id=" + id);
        }

        if (!StringUtils.equals(existing.getServiceName(), resource.getServiceName())) {
            throw new Exception("Attempt to change service-name for existing service-resource");
        }

        RangerServiceResourceSignature serializer = new RangerServiceResourceSignature(resource);

        resource.setId(existing.getId());
        resource.setGuid(existing.getGuid());
        resource.setResourceSignature(serializer.getSignature());
    }

    public void preUpdateServiceResourceByGuid(String guid, RangerServiceResource resource) throws Exception {
        if (StringUtils.isBlank(resource.getServiceName()) || MapUtils.isEmpty(resource.getResourceElements())) {
            throw new Exception("No serviceName or resource in RangerServiceResource");
        }

        RangerServiceResource existing = tagStore.getServiceResourceByGuid(guid);
        if (existing == null) {
            throw new Exception("Attempt to update nonexistent resource, guid=" + guid);
        }

        if (!StringUtils.equals(existing.getServiceName(), resource.getServiceName())) {
            throw new Exception("Attempt to change service-name for existing service-resource");
        }

        RangerServiceResourceSignature serializer = new RangerServiceResourceSignature(resource);

        resource.setId(existing.getId());
        resource.setGuid(guid);
        resource.setResourceSignature(serializer.getSignature());
    }

    public RangerServiceResource preDeleteServiceResource(Long id) throws Exception {
        RangerServiceResource existing = tagStore.getServiceResource(id);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent resource, id=" + id);
        }

        List<RangerTagResourceMap> associations = tagStore.getTagResourceMapsForResourceId(existing.getId());
        if (CollectionUtils.isNotEmpty(associations)) {
            throw new Exception("Attempt to delete serviceResource which is associated with a tag, id=" + id);
        }

        return existing;
    }

    public RangerServiceResource preDeleteServiceResourceByGuid(String guid, boolean deleteReferences) throws Exception {
        RangerServiceResource existing = tagStore.getServiceResourceByGuid(guid);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent resource, guid=" + guid);
        }

        List<RangerTagResourceMap> associations = tagStore.getTagResourceMapsForResourceId(existing.getId());
        if (CollectionUtils.isNotEmpty(associations) && !deleteReferences) {
            throw new Exception("Attempt to delete serviceResource which is associated with a tag, guid=" + guid);
        }

        return existing;
    }

    public RangerTagResourceMap preCreateTagResourceMap(String tagGuid, String resourceGuid) throws Exception {
        if (StringUtils.isBlank(resourceGuid) || StringUtils.isBlank(tagGuid)) {
            throw new Exception("Both resourceGuid and resourceId need to be non-empty");
        }

        RangerTagResourceMap existing = tagStore.getTagResourceMapForTagAndResourceGuid(tagGuid, resourceGuid);

        if (existing != null) {
            throw new Exception("Attempt to create existing association between resourceId=" + resourceGuid + " and tagId=" + tagGuid);
        }

        RangerServiceResource existingServiceResource = tagStore.getServiceResourceByGuid(resourceGuid);

        if (existingServiceResource == null) {
            throw new Exception("No resource found for guid=" + resourceGuid);
        }

        RangerTag existingTag = tagStore.getTagByGuid(tagGuid);

        if (existingTag == null) {
            throw new Exception("No tag found for guid=" + tagGuid);
        }

        RangerTagResourceMap newTagResourceMap = new RangerTagResourceMap();
        newTagResourceMap.setResourceId(existingServiceResource.getId());
        newTagResourceMap.setTagId(existingTag.getId());

        return newTagResourceMap;
    }

    public RangerTagResourceMap preCreateTagResourceMapByIds(Long tagId, Long resourceId) throws Exception {
        RangerTagResourceMap existing = tagStore.getTagResourceMapForTagAndResourceId(tagId, resourceId);

        if (existing != null) {
            throw new Exception("Attempt to create existing association between resourceId=" + resourceId + " and tagId=" + tagId);
        }

        RangerServiceResource existingServiceResource = tagStore.getServiceResource(resourceId);

        if (existingServiceResource == null) {
            throw new Exception("No resource found for id=" + resourceId);
        }

        RangerTag existingTag = tagStore.getTag(tagId);

        if (existingTag == null) {
            throw new Exception("No tag found for id=" + tagId);
        }

        RangerTagResourceMap newTagResourceMap = new RangerTagResourceMap();
        newTagResourceMap.setResourceId(resourceId);
        newTagResourceMap.setTagId(tagId);

        return newTagResourceMap;
    }

    public RangerTagResourceMap preDeleteTagResourceMap(Long id) throws Exception {
        RangerTagResourceMap existing = tagStore.getTagResourceMap(id);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent tagResourceMap(id=" + id + ")");
        }

        return existing;
    }

    public RangerTagResourceMap preDeleteTagResourceMapByGuid(String guid) throws Exception {
        RangerTagResourceMap existing = tagStore.getTagResourceMapByGuid(guid);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent tagResourceMap(guid=" + guid + ")");
        }

        return existing;
    }

    public RangerTagResourceMap preDeleteTagResourceMap(String tagGuid, String resourceGuid) throws Exception {
        RangerTagResourceMap existing = tagStore.getTagResourceMapForTagAndResourceGuid(tagGuid, resourceGuid);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent association between resourceId=" + resourceGuid + " and tagId=" + tagGuid);
        }

        return existing;
    }

    public RangerTagResourceMap preDeleteTagResourceMapByIds(Long tagId, Long resourceId) throws Exception {
        RangerTagResourceMap existing = tagStore.getTagResourceMapForTagAndResourceId(tagId, resourceId);

        if (existing == null) {
            throw new Exception("Attempt to delete nonexistent association between resourceId=" + resourceId + " and tagId=" + tagId);
        }

        return existing;
    }
}
